﻿
package extremefx.conditions {
	import extremefx.IEnumerable;
	import extremefx.IEnumerator;
	import extremefx.IEquatable;
	import extremefx.extensions.isNullOrEmpty;
	import extremefx.i18n.XString;

	/**
	 * @author Marcelo Volmaro
	 */
	internal class Validator {
		protected var _additionalMessage:String;
		protected var _argumentName:String;
		protected var _value:*;

		public function Validator(pValue:*, pArgumentName:String, pAdditionalMessage:String = null) {
			_argumentName = pArgumentName || "value";
			_value = pValue;
			_additionalMessage = pAdditionalMessage;
		}
		
		public function isOfType(pType:Class, pDescription:String = null):Validator {
			if (_value is pType) return this;
			
			throw _buildException(
				_getFormattedConditionMessage(
					"{0} should be of type {1}", 
					pDescription, 
					_argumentName,
					pType
				),
				
				TypeError
			);
		}
		
		public function isNotOfType(pType:Class, pDescription:String = null):Validator {
			if (!(_value is pType)) return this;
			
			throw _buildException(
				_getFormattedConditionMessage(
					"{0} should not be of type {1}", 
					pDescription, 
					_argumentName,
					pType
				),
				
				TypeError
			);
		}
		
		public function isNull(pDescription:String = null):Validator {
			if (_value != null) 
				throw _buildException(
					_getFormattedConditionMessage("{0} should be null", pDescription, _argumentName)
				);
			
			return this;
		}
		
		public function isNotNull(pDescription:String = null):Validator {
			if (_value == null) 
				throw _buildException(
					_getFormattedConditionMessage("{0} should not be null", pDescription, _argumentName)
				);
			
			return this;
		}
		
		public function isSameAs(pValue:*, pDescription:String = null):Validator {
			var same:Boolean;
			
			if (_value is IEquatable){
				same = IEquatable(_value).equals(pValue);
					
			} else if (pValue is IEquatable){
				same = IEquatable(pValue).equals(_value);
				
			} else {
				same = _value === pValue;
			}
			
			if (_isSame(pValue)) return this;
			 
			throw _buildException(
				_getFormattedConditionMessage(
					Resources.m_eqA, 
					pDescription, 
					_argumentName,
					pValue)
			);
		}
		
		public function isNotSameAs(pValue:*, pDescription:String = null):Validator {
			if (!_isSame(pValue)) return this;
			
			throw _buildException(
				_getFormattedConditionMessage(
					Resources.m_notEqA, 
					pDescription, 
					_argumentName,
					pValue)
			);
		}
		
		private function _isSame(pValue:*):Boolean {
			if (_value is IEquatable){
				return IEquatable(_value).equals(pValue);
					
			} else if (pValue is IEquatable){
				return IEquatable(pValue).equals(_value);
			}
			
			return _value === pValue;
		}
		
		public function setType(pType:Boolean = false):void {
			_buildException = pType ? _ensBuildException : _reqBuildException;
		}
		
		protected var _buildException:Function;
		
		protected function _ensBuildException(pCondition:String, pAdditionalMessage:String = null):Error{
			var exceptionMessage:String = XString.format("Postcondition '{0}' failed.", pCondition);

			if (!isNullOrEmpty(pAdditionalMessage)) {
				exceptionMessage += " " + pAdditionalMessage;
			}

			if (!isNullOrEmpty(_additionalMessage)) {
				exceptionMessage += " " + _additionalMessage;
			}

			return new PostconditionError(exceptionMessage);
		}
		
		protected function _reqBuildException(pCondition:String, pType:Class = null):Error{
			var message:String;
			
			var addMsg:String = null;
			if (_value == null || _value.toString() != "Object"){
				addMsg = XString.format("The actual value is {0}.", _stringify(_value));
			}
			
			if (addMsg == null){
				message = pCondition + ".";
				
			} else {
				message = pCondition + ". " + addMsg;
			}
			
			message = _buildMessage(_argumentName, message);
			
			if (pType is Error) return new pType(message);
			return new ArgumentError(message);

		}

		protected function _buildMessage(pArgument:String, pMessage:String):String {
			if (isNullOrEmpty(pArgument)) return pMessage;
			if (isNullOrEmpty(pMessage)) return pArgument;
			
			return "On {"+pArgument+"}:" + pMessage;
		}
		
		protected function _getFormattedConditionMessage(pError:String, pDescription:String, pArgumentName:String, pArg1:* = null, pArg2:* = null):String {
			return XString.format(
				isNullOrEmpty(pDescription) ? pError : pDescription, 
					pArgumentName, 
					_stringify(pArg1),
					_stringify(pArg2)
				);
		}
		
		protected function _stringify(pObj:*):String {
			if (pObj == null) return "null";
			if (pObj is String) return "'"+pObj+"'";
			
			if (pObj is IEnumerable){
				var c:IEnumerator = IEnumerable(pObj).getEnumerator();
				var a:Vector.<String> = new Vector.<String>();
				while (c.moveNext()){
					 a.push(_stringify(c.current));
				}
				
				return "{" + a.join(",") + "}";
			}
			
			return pObj.toString();
		}
	}
}
